#include <string>
#include <fstream>
#include <vector>
#include <iostream>
#include <QWheelEvent>
#include <cfloat>

#include "widget.h"
#include "ui_widget.h"
#include "global_var.h"

//using namespace std;

extern std::string sorted_csv_filename;

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    /**
     * add line in Constructor function
     * setName: set series name
     */
    QLineSeries* lineSeries = new QLineSeries();
    // 创建视图并显示图表
    chartView = new QChartView(this);

    /**
     * 1. get .csv file name
     * 2. get time std::string, format `MM-dd hh:mm:ss`
     *    https://lxblog.com/qianwen/share?shareId=c07a44dc-a731-4708-97cc-ce9b9c3fbce4
     */
    /**
     * https://lxblog.com/qianwen/share?shareId=bd7f3d57-c312-4b09-a29b-0cb30d42d941
     * 读.csv
     * 逐行读，取对应的所需的列
     *
     * 写入(time,temp)点，时间初始格式为字符串
     * QString timeString = "11-19 11:25:30"; // 假设这是你的输入时间字符串
     *     QDateTime dateTime = QDateTime::fromString(timeString, "MM-dd hh:mm:ss");
     *     series->append(dateTime.toMSecsSinceEpoch(), 10); // 假设y值为10
     */
    std::cout<<"draw file : "<<"\""<<sorted_csv_filename<<"\""<<std::endl;
    std::ifstream output_csv_file(sorted_csv_filename);
    std::string line_cur;
    /* 跳过第一行表头 */
    getline(output_csv_file, line_cur);
    while(getline(output_csv_file, line_cur)){
        /* find board sensor temp column */
        std::vector<int> column_need_draw;
        /** 
         * 初版实现基于以下观点
         *  1. 第一列是时间列
         *  2. 固定查找第三列的壳温（后续壳温不在第三列就有问题）
         * todo改进：
         *  1. 保存时间列索引 & 需要绘制列的索引（可以放到配置文件中）
         */
        if(line_cur.size()==0){
            continue;
        }
        size_t value_beg=0;
        {
            int i=0;
            while(i<2 && value_beg<line_cur.size()){
                if(line_cur[value_beg] == ','){
                    i++;
                }
                value_beg++;
            }
        }
        /*
         * 上面value_beg就指向第三列开始的位置
         */
        size_t value_end=value_beg;
        value_end=line_cur.find_first_of(',',value_beg);
        if(value_end == std::string::npos){
            /* end指向最后一个字符再下一个字符 */
            value_end = line_cur.size();
        }
        std::string value_str = line_cur.substr(value_beg, (value_end-value_beg));
        // debug
        //cout<<"value_str = "<<value_str<<endl;
        int value = stoi(value_str);
        size_t time_str_beg=0;
        size_t time_str_end = line_cur.find_first_of(',');
        std::string time_str = line_cur.substr(time_str_beg, (time_str_end-time_str_beg));
        /* 至此time_str和value都有了 */
        QString time_str_qt = QString::fromStdString(time_str);
        QDateTime time = QDateTime::fromString(time_str_qt, "MM-dd hh:mm:ss");

        /* 这里把(time, temp)加入了QLineSeries* lineSeries */
        lineSeries->append(time.toMSecsSinceEpoch(), value);
    }

    // 创建图表并添加系列
    mChart = new QChart();
    // 隐藏图例
    //mChart->legend()->hide();
    // 将数据系列（QLineSeries）添加到图表中
    mChart->addSeries(lineSeries);
    // 创建默认的轴
    /* 添加默认轴会导致有两个横坐标、两个纵坐标，所以注释掉这行 */
    //mChart->createDefaultAxes();
    mChart->setTheme(QChart::ChartThemeDark);
    mChart->setTitle("time-temp");
    // 设置X轴为时间轴
    // 这里未设置时间轴范围
    axis_x = new QDateTimeAxis;
    // 设置显示格式
    axis_x->setFormat("MM-dd hh:mm:ss");
    axis_x->setTitleText("time");
    // 设置Y轴为数值轴
    QValueAxis *axis_y = new QValueAxis;
    axis_y->setTitleText("temp");
    // 将轴添加到图表中
    mChart->addAxis(axis_x, Qt::AlignBottom);
    mChart->addAxis(axis_y, Qt::AlignLeft);
    // 将轴绑定到系列上
    lineSeries->attachAxis(axis_x);
    lineSeries->attachAxis(axis_y);


    lineSeries->setName("virtual-sensor");
    chartView->setChart(mChart);
    /**
     * 这是设置图表大小，这样做会导致图表和主窗口割裂
     * eg: 图表过大，主窗口只能显示左上的部分图表，需要放大主窗口之后才能显示全部内容
     */
    //chartView->resize(QSize(1600,900));
    /* Eliminate edges to make it look smoother */
    chartView->setRenderHints(QPainter::Antialiasing);

    /* 图表大小设置的正确做法：使用布局管理器管理QChartView */
    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->addWidget(chartView);
    setLayout(layout);

    /** 
     * 查找最近的时间点
     * _min保存了放缩后的最小坐标，防止后续过度缩放横轴
     * _max保存了放缩后的最大坐标，防止后续过度放缩横轴
     * 横轴坐标下限是初始下限，上限是初始上限-120s
     * 横轴坐标上限是初始上限，下限是初始下限+120s
     */
    minTime_min = axis_x->min();
    minTime_max = axis_x->max().addSecs(-120);
    maxTime_max = axis_x->max();
    maxTime_min = axis_x->min().addSecs(120);

    /* 设置主窗口大小 */
    resize(1920, 1080);
}

/** 
 * todo:
 * 当前缩放横轴总是基于时间轴中点，希望将鼠标悬停位置作为中点进行缩放
 * https://www.tongyi.cn/qianwen/share?type=codePreview&shareId=5fb8ce62-af67-4e2b-b9df-58b3cdf761e7&type=codePreview
 */
void Widget::wheelEvent(QWheelEvent *event) {
    int delta = event->angleDelta().y();

    /* 获取鼠标悬停位置 */
    QPointF mousePos = event->position();

    /* 将鼠标位置映射到图表坐标系中 */
    QPointF viewportPos = chartView->viewport()->mapFromParent(mousePos.toPoint());
    QPointF scenePos = chartView->chart()->mapToValue(viewportPos);

    QDateTime minTime = axis_x->min();
    QDateTime maxTime = axis_x->max();

    /* 找到鼠标悬停位置对应的时间点 */
    qint64 centerMs = static_cast<qint64>(scenePos.x());

    /* 根据滚动方向调整范围 */
    /* 缩小范围（放大） */
    if (delta > 0) {
        minTime = QDateTime::fromMSecsSinceEpoch(centerMs-0.9*(centerMs-minTime.toMSecsSinceEpoch()));
        maxTime = QDateTime::fromMSecsSinceEpoch(centerMs+0.9*(maxTime.toMSecsSinceEpoch()-centerMs));
    }
    /* 扩大范围（缩小） */
    else {
        minTime = QDateTime::fromMSecsSinceEpoch(centerMs-1.1*(centerMs-minTime.toMSecsSinceEpoch()));
        maxTime = QDateTime::fromMSecsSinceEpoch(centerMs+1.1*(maxTime.toMSecsSinceEpoch()-centerMs));
    }

    /* 更新横轴的范围 */
    /* minTime太小 */
    minTime = (minTime<minTime_min)?minTime_min:minTime;
    /* minTime太大 */
    minTime = (minTime_max<minTime)?minTime_max:minTime;
    /* maxTime太小 */
    maxTime = (maxTime<maxTime_min)?maxTime_min:maxTime;
    /* maxTime太大 */
    maxTime = (maxTime_max<maxTime)?maxTime_max:maxTime;
    axis_x->setRange(minTime, maxTime);
}

/**
 * @brief 处理鼠标点击事件，显示最近的曲线上的点的坐标
 * @param event 鼠标点击事件
 */
void Widget::mousePressEvent(QMouseEvent *event) {
    /* 获取鼠标点击的位置 */
    QPointF mousePos = event->pos();
    /* 将鼠标位置转换为图表视图中的位置 */
    QPointF viewportPos = chartView->viewport()->mapFromParent(mousePos.toPoint());
    /* 将视图位置转换为图表中的值 */
    QPointF scenePos = chartView->chart()->mapToValue(viewportPos);

    /* 找到最近的曲线上的点 */
    /* 鼠标的横坐标值 */
    double xVal = scenePos.x();
    /* 最近的曲线上的点的纵坐标值 */
    double yVal = findNearestY(xVal);

    if (!std::isnan(yVal)) {
        /* 将横坐标值转换为时间 */
        QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(static_cast<qlonglong>(xVal));
        /* 创建工具提示文本 */
        QString tooltipText = QString("Time: %1\nTemp: %2")
                              .arg(dateTime.toString("MM-dd hh:mm:ss"))
                              .arg(yVal);
        /* 显示工具提示 */
        QToolTip::showText(event->globalPos(), tooltipText, this, QRect(), 3000);
    }
}

/**
 * @brief 查找给定横坐标最接近的曲线上点的纵坐标值
 * @param xVal 给定的横坐标值
 * @return 返回最接近的点的纵坐标值，如果找不到则返回 NAN
 */
double Widget::findNearestY(double xVal) {
    /* 获取第一个系列（假设只有一个系列） */
    QLineSeries* series = qobject_cast<QLineSeries*>(mChart->series().first());
    /* 如果没有系列，返回 NAN */
    if (!series) return NAN;

    /* 初始化最小差值为最大双精度浮点数 */
    double nearestXDiff = DBL_MAX;
    /* 初始化最近的纵坐标值为 NAN */
    double nearestY = NAN;

    /* 遍历所有点，找到最接近的点 */
    for (const auto &point : series->points()) {
        /* 计算当前点与给定横坐标的差值 */
        double diff = fabs(point.x() - xVal);
        /* 如果差值更小，则更新最近的点 */
        if (diff < nearestXDiff) {
            /* 更新最小差值 */
            nearestXDiff = diff;
            /* 更新最近的纵坐标值 */
            nearestY = point.y();
        }
    }
    /* 返回最接近的点的纵坐标值 */
    return nearestY;
}

Widget::~Widget()
{
    delete ui;
}
